/** @file

  Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Uefi.h>
#include <Omap3530/Omap3530.h>

#include <Library/DebugLib.h>
#include <Library/IoLib.h>
#include <Library/UefiBootServicesTableLib.h>

#include <Protocol/SmbusHc.h>

#define MAX_RETRY  1000

//
// Internal Functions
//
STATIC
EFI_STATUS
WaitForBusBusy (
  VOID
  )
{
  UINTN Retry = 0;

  while (++Retry < MAX_RETRY && (MmioRead16(I2C_STAT) & BB));

  if (Retry == MAX_RETRY) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
PollForStatus(
  UINT16 StatusBit
  )
{
  UINTN Retry = 0;

  while(Retry < MAX_RETRY) {
    if (MmioRead16(I2C_STAT) & StatusBit) {
      //Clear particular status bit from Status register.
      MmioOr16(I2C_STAT, StatusBit);
      break;
    }
    Retry++;
  }

  if (Retry == MAX_RETRY) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
ConfigureI2c (
  VOID
  )
{
  //Program prescaler to obtain 12-MHz clock
  MmioWrite16(I2C_PSC, 0x0000);

  //Program SCLL and SCLH
  //NOTE: Following values are the register dump after U-Boot code executed.
  //We need to figure out how its calculated based on the I2C functional clock and I2C_PSC.
  MmioWrite16(I2C_SCLL, 0x0035);
  MmioWrite16(I2C_SCLH, 0x0035);

  //Take the I2C controller out of reset.
  MmioOr16(I2C_CON, I2C_EN);

  //Initialize the I2C controller.

  //Set I2C controller in Master mode.
  MmioOr16(I2C_CON, MST);

  //Enable interrupts for receive/transmit mode.
  MmioOr16(I2C_IE, (XRDY_IE | RRDY_IE | ARDY_IE | NACK_IE));

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
I2CReadOneByte (
  UINT8 *Data
  )
{
  EFI_STATUS Status;

  //I2C bus status checking
  Status = WaitForBusBusy();
  if (EFI_ERROR(Status)) {
    return Status;
  }

  //Poll till Receive ready bit is set.
  Status = PollForStatus(RRDY);
  if (EFI_ERROR(Status)) {
    return Status;
  }

  *Data = MmioRead8(I2C_DATA);

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
I2CWriteOneByte (
  UINT8 Data
  )
{
  EFI_STATUS Status;

  //I2C bus status checking
  Status = WaitForBusBusy();
  if (EFI_ERROR(Status)) {
    return Status;
  }

  //Data transfer
  //Poll till Transmit ready bit is set
  Status = PollForStatus(XRDY);
  if (EFI_ERROR(Status)) {
    return Status;
  }

  MmioWrite8(I2C_DATA, Data);

  //Wait and check if the NACK is not set.
  gBS->Stall(1000);
  if (MmioRead16(I2C_STAT) & NACK) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
SmbusBlockRead (
  OUT UINT8       *Buffer,
  IN  UINTN       Length
  )
{
  UINTN      Index = 0;
  EFI_STATUS Status = EFI_SUCCESS;

  //Transfer configuration for receiving data.
  MmioWrite16(I2C_CNT, Length);
  //Need stop bit before sending data.
  MmioWrite16(I2C_CON, (I2C_EN | MST | STP | STT));

  while (Index < Length) {
    //Read a byte
    Status = I2CReadOneByte(&Buffer[Index++]);
    if (EFI_ERROR(Status)) {
      return Status;
    }
  }

  //Transfer completion
  Status = PollForStatus(ARDY);
  if (EFI_ERROR(Status)) {
    return Status;
  }

  return Status;
}

STATIC
EFI_STATUS
SmbusBlockWrite (
  IN UINT8       *Buffer,
  IN UINTN       Length
  )
{
  UINTN      Index = 0;
  EFI_STATUS Status = EFI_SUCCESS;

  //Transfer configuration for transmitting data
  MmioWrite16(I2C_CNT, Length);
  MmioWrite16(I2C_CON, (I2C_EN | TRX | MST | STT | STP));

  while (Index < Length) {
    //Send a byte
    Status = I2CWriteOneByte(Buffer[Index++]);
    if (EFI_ERROR(Status)) {
      return Status;
    }
  }

  //Transfer completion
  Status = PollForStatus(ARDY);
  if (EFI_ERROR(Status)) {
    return Status;
  }

  return Status;
}

//
// Public Functions.
//
EFI_STATUS
EFIAPI
SmbusExecute (
  IN CONST EFI_SMBUS_HC_PROTOCOL    *This,
  IN CONST EFI_SMBUS_DEVICE_ADDRESS SlaveAddress,
  IN CONST EFI_SMBUS_DEVICE_COMMAND Command,
  IN CONST EFI_SMBUS_OPERATION      Operation,
  IN CONST BOOLEAN                  PecCheck,
  IN OUT   UINTN                    *Length,
  IN OUT   VOID                     *Buffer
  )
{
  UINT8      *ByteBuffer  = Buffer;
  EFI_STATUS Status       = EFI_SUCCESS;
  UINT8      SlaveAddr    = (UINT8)(SlaveAddress.SmbusDeviceAddress);

  if (PecCheck) {
    return EFI_UNSUPPORTED;
  }

  if ((Operation != EfiSmbusWriteBlock) && (Operation != EfiSmbusReadBlock)) {
    return EFI_UNSUPPORTED;
  }

  //Set the Slave address.
  MmioWrite16(I2C_SA, SlaveAddr);

  if (Operation == EfiSmbusReadBlock) {
    Status = SmbusBlockRead(ByteBuffer, *Length);
  } else if (Operation == EfiSmbusWriteBlock) {
    Status = SmbusBlockWrite(ByteBuffer, *Length);
  }

  return Status;
}

EFI_STATUS
EFIAPI
SmbusArpDevice (
  IN CONST EFI_SMBUS_HC_PROTOCOL    *This,
  IN       BOOLEAN                  ArpAll,
  IN       EFI_SMBUS_UDID           *SmbusUdid OPTIONAL,
  IN OUT   EFI_SMBUS_DEVICE_ADDRESS *SlaveAddress OPTIONAL
  )
{
  return EFI_UNSUPPORTED;
}


EFI_STATUS
EFIAPI
SmbusGetArpMap (
  IN CONST EFI_SMBUS_HC_PROTOCOL    *This,
  IN OUT   UINTN                    *Length,
  IN OUT   EFI_SMBUS_DEVICE_MAP     **SmbusDeviceMap
  )
{
  return EFI_UNSUPPORTED;
}


EFI_STATUS
EFIAPI
SmbusNotify (
  IN CONST  EFI_SMBUS_HC_PROTOCOL     *This,
  IN CONST  EFI_SMBUS_DEVICE_ADDRESS  SlaveAddress,
  IN CONST  UINTN                     Data,
  IN CONST  EFI_SMBUS_NOTIFY_FUNCTION NotifyFunction
  )
{
  return EFI_UNSUPPORTED;
}

EFI_SMBUS_HC_PROTOCOL SmbusProtocol =
{
  SmbusExecute,
  SmbusArpDevice,
  SmbusGetArpMap,
  SmbusNotify
};

EFI_STATUS
InitializeSmbus (
    IN EFI_HANDLE       ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
    )
{
  EFI_HANDLE      Handle = NULL;
  EFI_STATUS      Status;

  //Configure I2C controller.
  Status = ConfigureI2c();
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "InitializeI2c fails.\n"));
    return Status;
  }

  // Install the SMBUS interface
  Status = gBS->InstallMultipleProtocolInterfaces(&Handle, &gEfiSmbusHcProtocolGuid, &SmbusProtocol, NULL);
  ASSERT_EFI_ERROR(Status);

  return Status;
}

